
package org.kolonitsky.alexey.utils
{

    import flash.events.TimerEvent;
    import flash.utils.Timer;
    import flash.utils.getTimer;

    import org.kolonitsky.alexey.debug.DebugWrapper;

    /**
     *
     */
    public class DateUtil
    {
        public static const ALL_TIME:String = "all";
        public static const LAST_TIME:String = "last";
        public static const DAY_PERIOD:String = "day";
        public static const WEEK_PERIOD:String = "week";
        public static const MONTH_PERIOD:String = "month";
        public static const SEASON_PERIOD:String = "season";
        public static const YEAR_PERIOD:String = "year";

        public static const MILISECONDS_PER_MIN:Number = 60000;
        public static const MILISECONDS_PER_HOUR:Number = MILISECONDS_PER_MIN * 60;
        public static const MILISECONDS_PER_DAY:Number = MILISECONDS_PER_HOUR * 24;
        public static const MILISECONDS_PER_WEEK:Number = MILISECONDS_PER_DAY * 7;
        public static const MILISECONDS_PER_MONTH:Number = MILISECONDS_PER_DAY * 30;
        public static const MILISECONDS_PER_SEASON:Number = MILISECONDS_PER_MONTH * 3;
        public static const MILISECONDS_PER_YEAR:Number = MILISECONDS_PER_MONTH * 12;

        public static const W3C_DATE_PATTERN:RegExp = /\d{4}(-\d{2}(-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,3})?)?(Z|[+\-]\d{2}:\d{2}))?)?)?/;
        public static const DATE_PERIOD_PATTERN:RegExp = /([0-9][dwmys]|all|last|day|week|month|season|year)/i

        public static const RFC822_DAY_PATTERN:RegExp = /(Mon|Tue|Wed|Thu|Fri|Sat|Sun)/i;
        public static const RFC822_MONTH_PATTERN:RegExp = /(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i;
        public static const RFC822_TIMEZONE_PATTERN:RegExp = /(UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[AJMNY]|[+\-]\d{4})/i;
        public static const RFC822_DATE_PATTERN:RegExp = /((Mon|Tue|Wed|Thu|Fri|Sat|Sun), )?\d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{2,4} \d{2}:\d{2}(:\d{2})? (UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[AJMNY]|[+\-]\d{4})/i;

        public static const HUMAN_TIME_PERIOD:Object =
        {
            d: MILISECONDS_PER_DAY,
            day: MILISECONDS_PER_DAY,
            
            w: MILISECONDS_PER_WEEK,
            week: MILISECONDS_PER_WEEK,
            
            m: MILISECONDS_PER_MONTH,
            month: MILISECONDS_PER_MONTH,
            
            y: MILISECONDS_PER_YEAR,
            year: MILISECONDS_PER_YEAR
        };

        private static var _APPLICATION_START_TIME:Date = new Date();
        private static var _ZERO_TIME:uint = getTimer();

        public static function get APPLICATION_START_TIME():Date
        {
            return _APPLICATION_START_TIME;
        }

        public static function get TODAY():Date
        {
            return new Date();
        }

        public static function get NOW():Date
        {
            return new Date();
        }

        public static function get TOMORROW():Date
        {
            var result:Date = new Date(NOW.time);
            result.date++;
            return result;
        }

        public static function get YESTERDAY():Date
        {
            var result:Date = new Date(NOW.time);
            result.date--;
            return result;
        }
        
        public static function get NEVER():Date
        {
            var result:Date = new Date(NOW.time);
            result.fullYear += 1000;
            return result;
        }

        public static const ZERO_TIME:Date = new Date(-2211753600000);

        /**
         * compate two dates with defined precision. Each dates rouned to
         * precision and compare times.
         *
         * @param a
         * @param b
         * @param precision
         * @return
         */
        public static function compare(a:Date,  b:Date,  precision:Number):int
        {
            var aTime:Number = a.time - (a.time % precision);
            var bTime:Number = b.time - (b.time % precision);
            if (aTime > bTime) return 1;
            if (aTime < bTime) return -1;
            return 0;
        }

        /**
         *
         * @param date
         * @param precision
         */
        public static function round(date:Date,  precision:Number):void
        {
            date.time = (date.time - (date.time % precision));
        }

        /**
         *  Convert human short date format to miliseconds periond.
         *
         *  @params time can be 1y for one year. 5w for five weeks.
         *  @return miliseconds for passed period or -1 if passed time incorrect.
         */
        public static function humanTimeToMiliseconds(time:String):Number
        {
            var timeRegexp:RegExp = /^(?P<a>\d{0,1})(?P<b>[dwmy])/ig;
            var result:Array = timeRegexp.exec(time);
            if (result)
            {
                var multiplicand:int = parseInt(result.a) || 1;
                var multiplier:Number = HUMAN_TIME_PERIOD[result.b];
                return multiplier * multiplicand;
            }
            else if (time in HUMAN_TIME_PERIOD)
            {
                return HUMAN_TIME_PERIOD[time];
            }
            else
            {
                return -1;
            }
        }

        public static function getGranulation(beginDate:Date,  endDate:Date):Number
        {
            var days:Number = (endDate.time - beginDate.time) / DateUtil.MILISECONDS_PER_DAY;
            var result:Number = 0.0;

            if (days > 0 && days <= 22)          // 22 days first three weeks only daily mode
                result = DateUtil.MILISECONDS_PER_DAY;
            else if (days > 22 && days <= 124)   // 17 weeks -> 4 x m
                result = DateUtil.MILISECONDS_PER_WEEK;
            else if (days > 124 && days <= 558) //  18 month -> 6 x 3m
                result = DateUtil.MILISECONDS_PER_MONTH;
            else if (days > 558 && days <= 1460)//  15 seasons -> 4 year
                result = DateUtil.MILISECONDS_PER_MONTH * 3;
            else
                result = DateUtil.MILISECONDS_PER_YEAR;
            return result;
        }

        public static function periodToString(period:Number):String
        {
            if (period == MILISECONDS_PER_DAY)
                return "day";
            else if (period == MILISECONDS_PER_HOUR)
                return "hour";
            else if (period == MILISECONDS_PER_MIN)
                return "minute";
            else if (period == MILISECONDS_PER_MONTH)
                return "month";
            else if (period == MILISECONDS_PER_SEASON)
                return "season";
            else if (period == MILISECONDS_PER_WEEK)
                return "week";
            else if (period == MILISECONDS_PER_YEAR)
                return "year";

            return "<BAD_PERIOD>";
        }

        public static function nextPeriod(date:Date, period:Number):Date
        {
            var result:Date = new Date(date.time);

            if (period == DateUtil.MILISECONDS_PER_MIN)
                result.minutes += 1;
            else if (period == DateUtil.MILISECONDS_PER_HOUR)
                result.hours += 1;
            else if (period == DateUtil.MILISECONDS_PER_DAY)
                result.date += 1;
            else if (period == DateUtil.MILISECONDS_PER_WEEK)
                result.date += 7;
            else if (period == DateUtil.MILISECONDS_PER_MONTH)
                result.month += 1;
            else if (period == DateUtil.MILISECONDS_PER_SEASON)
                result.month += 3;
            else if (period == DateUtil.MILISECONDS_PER_YEAR)
                result.fullYear += 1;
            else
                result.time += period;
            
            return result;
        }

        public static function createDate(unixTime:uint):Date
        {
            if (isNaN(unixTime))
                return new Date(0);

            var ms:Number = unixTime * 1000;// - localDate.timezoneOffset * MILISECONDS_PER_MIN;
            return new Date(ms);
        }

        public static function dateToUnix(date:Date):uint
        {
            if (date == null)
                return 0;
            
            if (date == NEVER)
                return Number(uint.MAX_VALUE);

            var ms:Number = date.time;// + date.timezoneOffset * MILISECONDS_PER_MIN;
            return (ms - (ms % 1000)) / 1000;
        }

        public static function toW3CDate(date:Date):String
        {
            if (date)
            {
                var monthString:String = String(date.month < 10 ? "0" + (date.month + 1) : date.month + 1);
                var dayString:String = String(date.day < 10 ? "0" + date.day : date.day);
                return date.fullYear.toString() + "-" + monthString + "-" + dayString;
            }
            return "<!INVALID_DATE>";
        }
        
        public static function toW3CTime(date:Date):String
        {
            if (date)
            {
                var secString:String = String(date.seconds < 10 ? "0" + date.seconds : date.seconds);
                var minString:String = String(date.minutes < 10 ? "0" + date.minutes : date.minutes);
                var hourString:String = String(date.hours < 10 ? "0" + date.hours : date.hours);
                return hourString + ":" + minString + ":" + secString;
            }
            return "<!INVALID_TIME>";
        }

        public static function toW3CDateTime(date:Date):String
        {
            return toW3CDate(date) + "T" + toW3CTime(date);
        }
                                                                                         
        public static function parseW3CDTF(source:String):Date
        {
            var r:Array = W3C_DATE_PATTERN.exec(source);
            if (r == null)
                return null;

            if (r.length >= 1 && r[0]) //year found
            {
                var strYear:String = r[0];
            }

            if (r.length >= 2 && r[1]) //month found
            {
                var strMonth:String = r[1];
                strYear = strYear.replace(strMonth, "");
                strMonth = strMonth.slice(1);
            }
            if (r.length >= 1 && r[2]) //date found
            {
                var strDate:String = r[2];
                strMonth = strMonth.replace(strDate, "");
                strDate = strDate.slice(1);
            }

            return new Date(strYear, parseInt(strMonth) - 1, strDate);
        }

    }
}
